using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Pegasus.UnitTests.Workflow.Service.Workflows;
using Pegasus.Workflow.Service;
using Pegasus.Workflow.Service.DefaultServices;
using Pegasus.Workflow.Service.FileServices;

namespace Pegasus.UnitTests.Workflow.Service
{
	/// <summary>
	/// Test the workflow services classes.
	/// </summary>
	[TestClass]
	public class WorkflowService2Tests
	{
		// Local Instance Values
		private TestContext m_testContextInstance;
		private string m_dataDir;

		private int m_timeoutSec = 30;
		private int m_waitTimeMS = 500;

		/// <summary>
		/// Initializes a new instance of the <see cref="T:WorkflowService2Tests"/> class.
		/// </summary>
		public WorkflowService2Tests()
		{
			 m_dataDir = Path.Combine( Environment.CurrentDirectory, "WorkflowData" );
		}

		/// <summary>
		/// Gets or sets the test context which provides information about and 
		/// functionality for the current test run.
		/// </summary>
		/// <value>The test context.</value>
		public TestContext TestContext
		{
			get
			{
				return m_testContextInstance;
			}
			set
			{
				m_testContextInstance = value;
			}
		}

		/// <summary>
		/// Test that the IWorkflowService interface methods are called in the correct order.
		/// </summary>
		[TestMethod]
		public void StartStopWorkflowServiceTest()
		{
			// Start the workflow service
			WorkflowService wfService = StartWorkflowService();
			TestDataExchangeService dataService;
			try
			{
				dataService = wfService.GetService<TestDataExchangeService>();
				Assert.IsNotNull( dataService );
				Assert.IsNotNull( dataService.WorkflowService, "WorkfowService property not set" );
				Assert.AreSame( wfService, dataService.WorkflowService, "Different workflow services" );
				Assert.IsTrue( dataService.StartCalled, "OnStart method was not called" );
				Assert.IsFalse( dataService.StopCalled, "OnStop method was called" );
			}
			finally
			{
				// Stop workflow service
				StopWorkflowService( wfService );
			}

			Assert.IsTrue( dataService.StopCalled, "OnStop method was not called" );
		}

		/// <summary>
		/// Creates the workflow test #1.
		/// </summary>
		[TestMethod]
		public void CreateWorkflowTest1()
		{
			// Start the workflow service
			WorkflowService wfService = StartWorkflowService();

			try
			{
				int workflowId = wfService.StartWorkflow( typeof( TestStartupWorkflow ), 1, "test arguement" );
				int exitCode = WaitForWorkflowToComplete( wfService, workflowId );

				Assert.IsTrue( exitCode == 10, "The wrong exit code was returned." );
			}
			finally
			{
				// Stop workflow service
				StopWorkflowService( wfService );
			}
		}

		/// <summary>
		/// Creates the workflow test #2.  Pass invalid startup args.
		/// </summary>
		[TestMethod]
		[ExpectedException( typeof( ArgumentException ) )]
		public void CreateWorkflowTest2()
		{
			// Start the workflow service
			WorkflowService wfService = StartWorkflowService();

			try
			{
				int workflowId = wfService.StartWorkflow( typeof( TestStartupWorkflow ), 0 );
			}
			finally
			{
				// Stop workflow service
				StopWorkflowService( wfService );
			}
		}

		/// <summary>
		/// Test that the data exchange service and events are called in the correctly.
		/// </summary>
		[TestMethod]
		public void WorkflowDataExchangeTest()
		{
			// Start the workflow service
			WorkflowService wfService = StartWorkflowService();

			try
			{
				// Get the data exchange service
				TestDataExchangeService dataService = wfService.GetService<TestDataExchangeService>();
				Assert.IsNotNull( dataService, "Missing data exchange service" );

				// Start the workflow
				int workflowId = wfService.StartWorkflow( typeof( TestEventWorkflow ) );
				WaitForWorkflowToBeInState( wfService, workflowId, "StateCatchFirstEvent" );

				// Fire the first event
				dataService.FireFirstEvent( new TestingEventArgs( workflowId, 5 ) );
				WaitForWorkflowToBeInState( wfService, workflowId, "StateCatchSecondEvent" );

				dataService.FireFirstEvent( new TestingEventArgs( workflowId, 5, true ) );

				// Fire the second event
				dataService.FireSecondEvent( new TestingEventArgs( workflowId, 6 ) );
				int exitCode = WaitForWorkflowToComplete( wfService, workflowId );

				Assert.IsTrue( exitCode == 123, "The wrong exit code was returned." );
			}
			finally
			{
				// Stop workflow service
				StopWorkflowService( wfService );
			}
		}

		/// <summary>
		/// Test that the parent and child workflow methods are called in the correctly.
		/// </summary>
		[TestMethod]
		public void ParentChildWorkflowTest()
		{
			// Start the workflow service
			WorkflowService wfService = StartWorkflowService();

			try
			{
				// Start the parent workflow
				int workflowId = wfService.StartWorkflow( typeof( TestParentWorkflow ) );

				// Wait for the parent to exit.
				int parentExitCode = WaitForWorkflowToComplete( wfService, workflowId );

				// Get the child workflow ids
				List<int> workflowIdList = null;
				using( WorkflowContext parentContext = wfService.GetWorkflow( workflowId, true ) )
				{
					workflowIdList = parentContext.Workflow.ChildWorkflowsIds;
				}

				Assert.IsNotNull( workflowIdList, "No child list was found on workflow." );
				Assert.IsTrue( workflowIdList.Count == 1, "Workflow list should have 1 item but it has {0}", workflowIdList.Count );

				// Make sure that the child has exited.
				int childWorkflowId = workflowIdList[ 0 ];
				int childExitCode = WaitForWorkflowToComplete( wfService, childWorkflowId );

				Assert.IsTrue( childExitCode == 10, "The child workflow has the wrong exit code {0}, expecting 10.", childExitCode );
				Assert.IsTrue( parentExitCode == 456, "The parent workflow has the wrong exit code {0}, expecting 456.", parentExitCode );
			}
			finally
			{
				// Stop workflow service
				StopWorkflowService( wfService );
			}
		}


		/// <summary>
		/// Test workflow timer events
		/// </summary>
		[TestMethod]
		public void WorkflowTimerTest()
		{
			// Start the workflow service
			WorkflowService wfService = StartWorkflowService();

			try
			{
				int workflowId = wfService.StartWorkflow( typeof( TestTimerWorkflow ) );
				int exitCode = WaitForWorkflowToComplete( wfService, workflowId );

				Assert.IsTrue( exitCode == 10, "The wrong exit code was returned." );
			}
			finally
			{
				// Stop workflow service
				StopWorkflowService( wfService );
			}
		}



		/// <summary>
		/// Starts the workflow service.
		/// </summary>
		/// <returns></returns>
		private WorkflowService StartWorkflowService()
		{
			// Setup the persistence directory
			if( !Directory.Exists( m_dataDir ) )
			{
				Directory.CreateDirectory( m_dataDir );
			}

			// Register our services
			WorkflowService wfService = new WorkflowService();
			wfService.RegisterService( new FilePersistenceService( m_dataDir ) );
			wfService.RegisterService( new FileTrackingService( Path.Combine( m_dataDir, "XmlTracking.xml" ) ) );
			wfService.RegisterService( new TestDataExchangeService() );

			// Reigster the test workflows
			wfService.RegisterWorkflow( typeof( TestStartupWorkflow ) );
			wfService.RegisterWorkflow( typeof( TestEventWorkflow ) );
			wfService.RegisterWorkflow( typeof( TestParentWorkflow ) );
			wfService.RegisterWorkflow( typeof( TestChildWorkflow ) );
			wfService.RegisterWorkflow( typeof( TestTimerWorkflow ) );

			// Start the serivce
			wfService.Start();
			return wfService;
		}

		/// <summary>
		/// Stops the workflow service.
		/// </summary>
		/// <param name="wfService">The wf service.</param>
		private void StopWorkflowService( WorkflowService wfService )
		{
			// Clean up workflow service
			wfService.Stop();
		}

		/// <summary>
		/// Waits the state of for workflow to be in.
		/// </summary>
		/// <param name="wfService">The wf service.</param>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="state">The state.</param>
		private void WaitForWorkflowToBeInState( WorkflowService wfService, int workflowId, string state )
		{
			string currentState;
			int count = 0;

			do
			{
				count++;
				Thread.Sleep( m_waitTimeMS );

				using( WorkflowContext context = wfService.GetWorkflow( workflowId, true ) )
				{
					currentState = context.Workflow.CurrentState;
				}
			}
			while( count < m_timeoutSec && currentState != state );

			// Throw if we took too long.
			if( count >= m_timeoutSec )
			{
				throw new TimeoutException( "Workflow didn't complete within timeout value" );
			}
		}

		/// <summary>
		/// Waits for the workflow to complete.
		/// </summary>
		/// <param name="wfService">The wf service.</param>
		/// <param name="workflowId">The workflow id.</param>
		private int WaitForWorkflowToComplete( WorkflowService wfService, int workflowId )
		{
			bool hasCompleted = false;
			int exitCode = -1;
			int count = 0;

			do
			{
				count++;
				Thread.Sleep( m_waitTimeMS );

				using( WorkflowContext context = wfService.GetWorkflow( workflowId, true ) )
				{
				    hasCompleted = context.Workflow.HasCompleted;
					exitCode = context.Workflow.ExitCode;
				}
			}
			while( count < m_timeoutSec && !hasCompleted );

			// Throw if we took too long.
			if( count >= m_timeoutSec )
			{
				throw new TimeoutException( "Workflow didn't complete within timeout value" );
			}

			return exitCode;
		}
	}
}
